'
' Part of the examples framework... imported by the examples.
'
SuperStrict

Import BRL.FreeTypeFont
Import "render.bmx"

Global viewZoom:Float = 1
Global tx:Int, ty:Int, tw:Int = 800, th:Int = 600
Global viewCenter:b2Vec2 = New b2Vec2.Create(0.0, 0.0)
Global mouseState:Int = False

Function Run(tst:Test, settings:TSettings)

	detailFont = LoadImageFont(CurrentDir() + "/04B_03__.ttf", 8)

	SetOrigin(400,500)
	
	While Not KeyDown(KEY_ESCAPE)
	
		Cls
		
		Mouse(tst)
		
		tst.Keyboard()
		
		tst.DoStep(settings)

		Flip
	
	Wend

End Function

Function Mouse(tst:Test)

	Local x:Int = MouseX()
	Local y:Int = MouseY()

	' Use the mouse to move things around.
	If MouseDown(1) Then

		Local p:b2Vec2 = ConvertScreenToWorld(x, y)

		' button pressed
		If Not mouseState Then
		
			tst.MouseDown(p)
		
		End If

		mouseState = True
		
		tst.MouseMove(p)
	Else
	
		If mouseState Then

			Local p:b2Vec2 = ConvertScreenToWorld(x, y)

			tst.MouseUp(p)
		End If
		
		mouseState = False
		
	End If
	
End Function


Type QueryCallback Extends b2QueryCallback
	
	Field m_point:b2Vec2
	Field m_fixture:b2Fixture
	
	Method Create:QueryCallback(point:b2Vec2)
		m_point = point
		m_fixture = Null
		Return Self
	End Method

	Rem
		bbdoc:Called for each fixture found in the query AABB.
	End Rem
	Method ReportFixture:Int(fixture:b2Fixture)
		
		local body:b2Body = fixture.GetBody()

		if body
			
			if body.GetType() = b2_dynamicBody
				
				if fixture.TestPoint(m_point)
				
					m_fixture = fixture
					' We are done, terminate the query.
					Return False
					
				EndIf
			
			End If
					
		End If

		' Continue the query.
		Return True
		
	End Method

End Type

Type Test

	Const k_maxContactPoints:Int = 2048

	Field m_groundBody:b2Body
	Field m_worldAABB:b2AABB
	Field m_points:ContactPoint[] = New ContactPoint[k_maxContactPoints]
	Field m_pointCount:Int
	Field m_destructionListener:DestructionListener = New DestructionListener
'	Field m_contactListener:ContactListener = New ContactListener
	Field m_debugDraw:b2Draw = New debugDraw
	Field m_textLine:Int
	Field m_world:b2World
	Field m_bomb:b2Body
	Field m_mouseJoint:b2MouseJoint
	Field m_bombSpawnPoint:b2Vec2
	Field m_bombSpawning:Int
	Field m_mouseWorld:b2Vec2
	Field m_stepCount:Int
	
'	Field m_maxProfile:b2Profile
'	Field m_totalProfile:b2Profile
	
	Field _settings:TSettings
	
	Method Init(xs:Float = 0, ys:Float = 0)

		If xs Then
			xScale = xs
		End If
		If ys Then
			yScale = ys
		End If
	
		Local gravity:b2Vec2 = New b2Vec2.Create(0.0, -10.0)
		m_world = New b2World.Create(gravity)

'		m_contactListener._test = Self
		m_destructionListener._test = Self

		m_world.SetDebugDraw(m_debugDraw)
'		m_world.SetContactListener(m_contactListener)
		m_world.SetDestructionListener(m_destructionListener)
		
		local bodyDef:b2BodyDef = new b2BodyDef
		m_groundBody = m_world.CreateBody(bodyDef)
		
	End Method

	Method DoStep(settings:TSettings)
	
		_settings = settings
	
		m_textLine = 10

		Local timeStep:Float
		If settings.hz > 0.0 Then
			timeStep = 1.0 / settings.hz
		Else
			timeStep = 0.0
		End If
	
		If settings.pause Then
			If settings.singleStep Then
				settings.singleStep = 0
			Else
				timeStep = 0.0
			End If
	
		End If

		Local flags:Int = 0
		flags:+settings.drawShapes * b2Draw.e_shapeBit
		flags:+settings.drawJoints * b2Draw.e_jointBit
		flags:+settings.drawAABBs * b2Draw.e_aabbBit
		flags:+settings.drawCOMs * b2Draw.e_centerOfMassBit
		m_debugDraw.SetFlags(flags)

		m_world.SetAllowSleeping(settings.enableSleep)
		m_world.SetWarmStarting(settings.enableWarmStarting)
		m_world.SetContinuousPhysics(settings.enableContinuous)
		m_world.SetSubStepping(settings.enableSubStepping)

		m_pointCount = 0

		m_world.DoStep(timeStep, settings.velocityIterations, settings.positionIterations)
	
		m_world.DrawDebugData()
		
		If m_mouseJoint Then

			Local p1:b2Vec2 = m_mouseJoint.GetAnchorB()
			Local p2:b2Vec2 = m_mouseJoint.GetTarget()
			
			debugDraw(m_debugDraw).DrawPoint(p1, 4.0, b2Color.Set(0, 255, 0))
			debugDraw(m_debugDraw).DrawPoint(p2, 4.0, b2Color.Set(0, 255, 0))
		
			m_debugDraw.DrawSegment(p1, p2, b2Color.Set(204, 204, 204))
			
		End If

		
		If settings.drawContactPoints Then

			const k_impulseScale:Int = 0.1
			const k_axisScale:Int = 0.3

			For Local i:Int = 0 Until m_pointCount

				Local point:ContactPoint = m_points[i]
	
				If point.state = 0 Then
					' Add
					debugDraw(m_debugDraw).DrawPoint(point.position, 5.0, b2Color.Set(76, 242, 76))
				Else If point.state = 1 Then
					' Persist
					debugDraw(m_debugDraw).DrawPoint(point.position, 3.0, b2Color.Set(76, 76, 242))
				Else
					' Remove
					debugDraw(m_debugDraw).DrawPoint(point.position, 5.0, b2Color.Set(242, 76, 76))
				End If
				
				If settings.drawContactNormals = 1
					Local p1:b2Vec2 = point.position
					Local p2:b2Vec2 = p1.AddNew(point.normal.MultiplyNew(k_axisScale))
					debugDraw(m_debugDraw).DrawSegment(p1, p2, b2Color.Set(102, 229, 102))
				Else If settings.drawContactImpulse = 1
					Local p1:b2Vec2 = point.position
					Local p2:b2Vec2 = p1.AddNew(point.normal.MultiplyNew(k_impulseScale * point.normalImpulse))
					debugDraw(m_debugDraw).DrawSegment(p1, p2, b2Color.Set(229, 229, 76)) ;
				EndIf
				
				If settings.drawFrictionImpulse = 1
					Local tangent:b2Vec2 = b2CrossVectorScalar(point.normal, 1.0)
					Local p1:b2Vec2 = point.position
					Local p2:b2Vec2 = p1.AddNew(tangent.Multiply(point.tangentImpulse * k_impulseScale))
					debugDraw(m_debugDraw).DrawSegment(p1, p2, b2Color.Set(229, 229, 76)) ;
				EndIf
				
			Next
		End If
	
	End Method
	
	Method Keyboard()
		If KeyHit(KEY_F) Then
			debugDraw(m_debugDraw).showDetails = Not debugDraw(m_debugDraw).showDetails
		End If
		
		If KeyHit(KEY_C) Then
			_settings.drawContactPoints = Not _settings.drawContactPoints
		End If
	End Method

	Method DrawString(text:String, x:Int, y:Int)
	
		SetColor(255, 255, 255)
		
		Local ox:Float, oy:Float
		GetOrigin(ox, oy)
		
		SetOrigin 0,0
		
		DrawText text, x, y
		
		SetOrigin ox, oy
		
	End Method
	
	Method ShiftMouseDown(p:b2Vec2)
	End Method
	
	Method MouseDown(p:b2Vec2)
		m_mouseWorld = p

		If m_mouseJoint Then
			Return
		End If
		
		' Make a small box.
		Local aabb:b2AABB = New b2AABB.Create()
		Local d:b2Vec2 = New b2Vec2.Create(0.001, 0.001)
		aabb.SetLowerBound(p.SubtractNew(d))
		aabb.SetUpperBound(p.AddNew(d))

		' Query the world for overlapping shapes.
		Local callback:QueryCallback = new QueryCallback.Create(p)
		m_world.QueryAABB(callback, aabb)
		
		If callback.m_fixture
			
			Local body:b2Body = callback.m_fixture.GetBody()
			Local md:b2MouseJointDef = New b2MouseJointDef
			md.SetBodyA(m_groundBody)
			md.SetbodyB(body)
			md.SetTarget(p)
			md.SetMaxForce(1000.0 * body.GetMass())

			m_mouseJoint = b2MouseJoint(m_world.CreateJoint(md))
			body.SetAwake(True)
			
		End If

	End Method
	
	Method MouseUp(p:b2Vec2)
		If m_mouseJoint Then
			m_world.DestroyJoint(m_mouseJoint)
			m_mouseJoint = Null
		End If
	End Method

	Method MouseMove(p:b2Vec2)
		m_mouseWorld = p

		If debugDraw(m_debugDraw).showDetails Then
			SetImageFont(detailFont)
			SetColor(255, 255, 255)
			DrawText StripPlaces(p.X()) + "," + StripPlaces(p.Y()), MouseX() - 400, MouseY() - 500 - 10
			SetImageFont(Null)
		End If
		
		If m_mouseJoint Then
			m_mouseJoint.SetTarget(p)
		End If
		
	End Method
	
	Method JointDestroyed(joint:b2Joint)
	End Method
	
End Type


Type TSettings

	Field viewCenter:b2Vec2 = vec2(0.0, 20.0)
	Field hz:Float = 60
	Field velocityIterations:Int = 12	' The default valor of 8 ..
	Field positionIterations:Int = 12	' The default valor of 3 seems to be insufficient (BlitzMax execution time is slower than C++)
	Field drawShapes:Int = 1
	Field drawJoints:Int = 1
	Field drawAABBs:Int = 0
	Field drawContactPoints:Int = 0
	Field drawContactNormals:Int = 0
	Field drawContactImpulse:Int = 0
	Field drawFrictionImpulse:Int = 0
	Field drawCOMs:Int = 0
	Field drawStats:Int = 0
	Field drawProfile:Int = 0
	Field enableWarmStarting:Int = 1
	Field enableContinuous:Int = 1
	Field enableSubStepping:Int = 0
	Field enableSleep:Int = 1
	Field pause:Int = 0
	Field singleStep:Int = 0


End Type

Type ContactPoint

	Field fixtureA:b2Fixture
	Field fixtureB:b2Fixture
	Field normal:b2Vec2 
	Field position:b2Vec2 
	Field state:int
	Field normalImpulse:Float
	Field tangentImpulse:Float
	
End Type

'Type ContactListener Extends b2ContactListener
'
'	Field _test:Test
'	
'	Method Add(point:b2ContactPoint, contact:b2Contact)
'		If _test.m_pointCount = Test.k_maxContactPoints Then
'			Return
'		End If
'	
'		Local cp:ContactPoint = _test.m_points[_test.m_pointCount]
'		If Not cp Then
'			cp = New ContactPoint
'			_test.m_points[_test.m_pointCount] = cp
'		End If
'		
'		cp.shape1 = point.GetShape1()
'		cp.shape2 = point.GetShape2()
'		cp.position = point.GetPosition()
'		cp.normal = point.GetNormal()
''		cp.id = point->id
'		cp.state = ContactPoint.e_contactAdded
'	
'		_test.m_pointCount :+ 1
'	End Method
'	
'	Method Persist(point:b2ContactPoint, contact:b2Contact)
'		If _test.m_pointCount = Test.k_maxContactPoints Then
'			Return
'		End If
'	
'		Local cp:ContactPoint = _test.m_points[_test.m_pointCount]
'		If Not cp Then
'			cp = New ContactPoint
'			_test.m_points[_test.m_pointCount] = cp
'		End If
'		cp.shape1 = point.GetShape1()
'		cp.shape2 = point.GetShape2()
'		cp.position = point.GetPosition()
'		cp.normal = point.GetNormal()
''		cp.id = point->id
'		cp.state = ContactPoint.e_contactPersisted
'	
'		_test.m_pointCount :+ 1
'	End Method
'
'	Method Remove(point:b2ContactPoint, contact:b2Contact)
'		If _test.m_pointCount = Test.k_maxContactPoints Then
'			Return
'		End If
'	
'		Local cp:ContactPoint = _test.m_points[_test.m_pointCount]
'		If Not cp Then
'			cp = New ContactPoint
'			_test.m_points[_test.m_pointCount] = cp
'		End If
'		cp.shape1 = point.GetShape1()
'		cp.shape2 = point.GetShape2()
'		cp.position = point.GetPosition()
'		cp.normal = point.GetNormal()
''		cp.id = point->id
'		cp.state = ContactPoint.e_contactRemoved
'	
'		_test.m_pointCount :+ 1
'	End Method
'
'End Type

Type DestructionListener Extends b2DestructionListener


	Field _test:Test

	Method SayGoodbyeJoint(joint:b2Joint)
		If _test.m_mouseJoint = joint
			_test.m_mouseJoint = Null
		Else
			_test.JointDestroyed(joint)
		End If
	End Method

End Type

Function ConvertScreenToWorld:b2Vec2(x:Int, y:Int)

	Local ox:Float, oy:Float
	GetOrigin(ox, oy)
	
	x :- ox
	y :- oy

	Local wx:Float = x / xScale
	Local wy:Float = y / yScale
	
	Return Vec2(wx, -wy)
	
End Function